﻿using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Configuration;
using System.Collections.Generic;

namespace SBPweb.Forge.Communication.WCF
{

    public class CustomHost : ServiceHost
    {

        #region Private fields

        private Type tService;
        private Type tContract;

        #endregion

        #region Protected fields

        protected Dictionary<string, Uri> dictUrisByScheme = new Dictionary<string, Uri>();
        protected Dictionary<string, Binding> dictBindings = new Dictionary<string, Binding>();

        #endregion

        #region Constructor logic

        public CustomHost(Type serviceType, Type contractType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
            tService = serviceType;
            tContract = contractType;
            if (baseAddresses != null)
            {
                FixUpAddresses();
                FixUpBaseAddresses();
            }
            ServiceMetadataBehavior metadataBehavior = new ServiceMetadataBehavior();
            metadataBehavior.HttpGetEnabled = true;
            Description.Behaviors.Add(metadataBehavior);
        }

        #endregion

        #region Property accessors

        public static int HttpPort
        {
            get
            {
                return Convert.ToInt32(ConfigurationSettings.AppSettings["serviceHostHttpPort"]);
            }
        }

        public static int TcpPort
        {
            get
            {
                return Convert.ToInt32(ConfigurationSettings.AppSettings["serviceHostTcpPort"]);
            }
        }

        protected Type ServiceType
        {
            get
            {
                return tService;
            }
        }

        protected Type ContractType
        {
            get
            {
                return tContract;
            }
        }

        public bool IsCustomFaultBehaviorActive
        {
            get;
            set;
        }

        public bool IsServiceDebugBehaviorActive
        {
            get;
            set;
        }

        #endregion

        #region Methods

        #region Overridden

        protected override void InitializeRuntime()
        {
            CreateBindings();
            CreateExtensions();
            FixUpBehaviors();
            base.InitializeRuntime();
        }

        protected virtual void CreateBindings()
        {
            Binding httpBinding = CreateBindingInstanceForHttp(ContractType, dictUrisByScheme["http"]);
            if (httpBinding != null)
            {
                this.AddServiceEndpoint(
                    ContractType,
                    httpBinding,
                    dictUrisByScheme["http"]);
            }
            dictBindings.Add("http", httpBinding);

            Binding tcpBinding = CreateBindingInstanceForTcp(ContractType, dictUrisByScheme["net.tcp"]);
            if (tcpBinding != null)
            {
                this.AddServiceEndpoint(
                    ContractType,
                    tcpBinding,
                    dictUrisByScheme["net.tcp"]);
            }
            dictBindings.Add("tcp", tcpBinding);

            Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
            if (dictUrisByScheme.ContainsKey("net.tcp"))
            {
                this.AddServiceEndpoint(
                    typeof(IMetadataExchange),
                    mexBinding,
                    string.Format("{0}/mex", dictUrisByScheme["net.tcp"]));
            }
            dictBindings.Add("tcp.mex", mexBinding);
        }

        protected virtual Binding CreateBindingInstanceForHttp(Type type, Uri address)
        {
            return new WSDualHttpBinding();
        }

        protected virtual Binding CreateBindingInstanceForTcp(Type type, Uri address)
        {
            return new NetTcpBinding();
        }

        protected virtual void CreateExtensions()
        {
            ;
        }

        #endregion

        #region Helper functions

        private void FixUpAddresses()
        {
            foreach (Uri uri in BaseAddresses)
            {
                if (!dictUrisByScheme.ContainsKey(uri.Scheme))
                {
                    dictUrisByScheme.Add(uri.Scheme, uri);
                }
            }
            FixUpAddress("http", "http", HttpPort);
            FixUpAddress("http", "net.tcp", TcpPort);
        }

        private void FixUpBaseAddresses()
        {
            UriSchemeKeyedCollection uskc = new UriSchemeKeyedCollection();
            foreach (KeyValuePair<string, Uri> kvp in dictUrisByScheme)
            {
                if (!BaseAddresses.Contains(kvp.Value))
                {
                    uskc.Add(kvp.Value);
                }
            }
            InitializeDescription(uskc);
        }

        private void FixUpAddress(string oldScheme, string newScheme, int newPort)
        {
            if (!dictUrisByScheme.ContainsKey(newScheme) && dictUrisByScheme.ContainsKey(oldScheme))
            {
                Uri oldUri = dictUrisByScheme[oldScheme];
                Uri newUri = new Uri(string.Format("{0}://{1}:{2}{3}", newScheme, oldUri.Host, newPort, oldUri.PathAndQuery));
                dictUrisByScheme.Add(newScheme, newUri);
            }
        }

        private void FixUpBehaviors()
        {
            if (IsServiceDebugBehaviorActive)
            {
                FixUpServiceDebugBehavior();
            }
            if (IsCustomFaultBehaviorActive)
            {
                FixUpCustomFaultBehavior();
            }
        }

        private void FixUpServiceDebugBehavior()
        {
            Type type = typeof(ServiceDebugBehavior);
            ServiceDebugBehavior sdb = null;
            if (Description.Behaviors.Contains(type))
            {
                sdb = Description.Behaviors[type] as ServiceDebugBehavior;
            }
            else
            {
                sdb = new ServiceDebugBehavior();
                Description.Behaviors.Add(sdb);
            }
            sdb.IncludeExceptionDetailInFaults = true;
        }

        private void FixUpCustomFaultBehavior()
        {
            Type type = typeof(CustomFaultBehavior);
            foreach (ServiceEndpoint ep in this.Description.Endpoints)
            {
                if (!ep.Behaviors.Contains(type))
                {
                    ep.Behaviors.Add(new CustomFaultBehavior());
                }
            }
        }

        #endregion

        #endregion

    }

}
